﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using JAMR.Rendering.Blocks;
using JAMR.Utils;
using OOGL;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using Substrate;
using Wintellect.PowerCollections;

using Vector3 = OpenTK.Vector3;
using Substrate.Core;

namespace JAMR.Rendering
{
    public class Scene
    {
        public Scene(IChunkManager chunks, IBlockManager blocks) 
        {
            _chunks = new List<ChunkRef>();
            _blocks = new List<RenderBlock>();

            _chunkManager = chunks;
            _blockManager = blocks;
        }

        private IChunkManager _chunkManager;
        private IBlockManager _blockManager;

        private IList<ChunkRef> _chunks;
        private IList<RenderBlock> _blocks;

        public void AddChunk(ChunkRef chunk)
        {
            _chunks.Add(chunk);
        }
        public void AddBlock(Vector3 globalCoord, BlockInfo block)
        {
            _blocks.Add(new RenderBlock()
                {
                    Coord = globalCoord,
                    ID = block.ID
                });
        }

        public Bitmap RenderScene(Camera camera)
        {
            // Prep blocks
            OrderedBag<RenderBlock> rblocks = new OrderedBag<RenderBlock>();

            foreach (var chunk in _chunks)
            {
                var blocks = chunk.Blocks;

                for (int x = 0; x < blocks.XDim; x++)
                    for (int z = 0; z < blocks.ZDim; z++)
                        for (int y = 0; y < blocks.YDim; y++)
                        {
                            int renderBlockId = blocks.GetID(x, y, z);

                            if (renderBlockId != 0)
                                rblocks.Add(new RenderBlock()
                                    {
                                        ID = renderBlockId,
                                        Coord = new Vector3(chunk.X * blocks.XDim + x, y, chunk.Z * blocks.ZDim + z),
                                        Camera = camera.CameraLoc
                                    });
                        }
            }

            foreach (var block in _blocks)
            {
                block.Camera = camera.CameraLoc;

                if (block.ID != 0)
                    rblocks.Add(block);
            }

            // Make buffer
            FrameBuffer fbo = new FrameBuffer(camera.ImageSize.Width, camera.ImageSize.Height);
            fbo.Use();

            // Make camera & background
            GL.ClearColor(1f, 1f, 1f, 0.0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            GL.Enable(EnableCap.DepthTest);

            Matrix4 projection = camera.Projection.ShallowCopy();
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);

            Matrix4 model = camera.ModelView.ShallowCopy();
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref model);

            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);

            // Draw
            BlockStore bstore = BlockStore.Instance;

            foreach (var block in rblocks)
            {
                RenderableBlock renderBlock = bstore.GetById(block.ID);

                renderBlock.Draw(block.Coord, 0, _blockManager);
            }

            Bitmap screenshot = GraphicUtils.GrabScreenshot(camera.ImageSize);

            return screenshot;
        }
    }

    class RenderBlock : IComparable<RenderBlock>
    {
        public int ID { get; set; }
        public Vector3 Coord { get; set; }
        public Vector3 Camera { get; set; }

        public int CompareTo(RenderBlock other)
        {
            if (other == null)
                return 0;

            if (other == this)
                return 0;

            float distThis = this.Coord.Distance(this.Camera);
            float distOther = other.Coord.Distance(other.Camera);

            if (distThis > distOther) return -1;
            if (distThis < distOther) return 1;

            return 0;
        }
    }
}
